import cv2
import time
import platform
from configobj import ConfigObj

from car.car_serial import CarSerial
from car.car_pca9685 import PCA9685
from car.car_controller import CarController
from od.recognition_single import RecognitionSingle
from cv.image_init import ImageInit
from cv.show_images import ShowImage
from car.car_timer import CarTimer
from cv.follow_line import FollowLine
from cv.find_intersection import FindIntersection
from cv.find_roadblock import FindRoadblock
from cv.find_zebra_crossing import FindZebraCrossing
from cv.video_writer import VideoWriter
from od.recognition import Recognition

class GuiCarBase:
    task_list = []

    def __init__(self, file_path='./gui/setting.ini'):
        self.conf = ConfigObj(file_path, encoding='utf8')
        cf = self.conf
        # camera
        if self.get_platform() == 0:
            self._line_camera = int(cf['camera']['line_camera'])
            self._od_camera = int(cf['camera']['line_camera'])
        else:
            self._line_camera = '/dev/video' + cf['camera']['line_camera']
            self._od_camera = '/dev/video' + cf['camera']['od_camera']

        # object_detection
        if cf['objects']['object_detection'] == "True":
            threshold = float(cf['object_detection']['threshold'])
            if cf['object_detection']['custom_model'] == "True":
                r_model = cf['object_detection']['rt_model']
                r_label = cf['object_detection']['rt_label']
                self.recognition = RecognitionSingle(self._od_camera, threshold=threshold,
                                                     rt_model=r_model, rt_labels=r_label)
            else:
                # self.recognition = RecognitionSingle(self._od_camera, threshold=threshold)
                self.recognition = Recognition(self._od_camera,frequency=30)
            self.recognition.event_function = self.e_recognition
            GuiCarBase.task_list.append(self.recognition)

        # image_init
        threshold = int(cf['image_init']['threshold'])
        if cf['image_init']['bitwise_not'] == "True":
            bit_not = True
        else:
            bit_not = False
        k_num = int(cf['image_init']['kernel_type']) + 2
        iterations = int(cf['image_init']['iterations'])
        self.image_init = ImageInit(threshold=threshold, bitwise_not=bit_not,
                                    kernel_type=(k_num, k_num), iterations=iterations)
        self.image_init.event_function = self.e_image_init
        GuiCarBase.task_list.append(self.image_init)

        # follow_line
        if cf['objects']['follow_line'] == "True":
            threshold = int(cf['follow_line']['threshold'])
            if int(cf['follow_line']['direction']) == 0:
                direction = True
            else:
                direction = False
            self.follow_line = FollowLine(threshold=threshold, direction=direction)
            self.follow_line.event_function = self.e_follow_line
            GuiCarBase.task_list.append(self.follow_line)

        # find_intersection
        if cf['objects']['find_intersection'] == "True":
            para = cf['find_intersection']
            radius = int(para['radius'])
            threshold = int(para['threshold'])
            d_time = float(para['delay_time'])
            r_count = int(para['repeat_count'])
            self.find_intersection = FindIntersection(radius=radius, threshold=threshold,
                                                      delay_time=d_time, repeat_count=r_count)
            self.find_intersection.event_function = self.e_find_intersection
            GuiCarBase.task_list.append(self.find_intersection)

        # find_zebra_crossing
        if cf['objects']['find_zebra_crossing'] == "True":
            para = cf['find_zebra_crossing']
            threshold = int(para['threshold'])
            count = int(para['floor_line_count'])
            d_time = int(para['delay_time'])
            self.find_zebra_crossing = FindZebraCrossing(threshold=threshold, floor_line_count=count, delay_time=d_time)
            self.find_zebra_crossing.event_function = self.e_find_zebra_crossing
            GuiCarBase.task_list.append(self.find_zebra_crossing)

        # find_roadblock
        if cf['objects']['find_roadblock'] == "True":
            para = cf['find_roadblock']
            hl = int(para['h_low'])
            hh = int(para['h_high'])
            sl = int(para['s_low'])
            sh = int(para['s_high'])
            vl = int(para['v_low'])
            vh = int(para['v_high'])
            threshold = float(para['threshold'])
            self.find_roadblock = FindRoadblock(h_low=hl, h_high=hh, s_low=sl, s_high=sh, v_low=vl, v_high=vh,
                                                threshold=threshold)
            self.find_roadblock.event_function = self.e_find_roadblock
            GuiCarBase.task_list.append(self.find_roadblock)

        # car_controller
        para = cf['car_controller']
        driver_type = int(para['type'])
        if driver_type == 0:
            if int(para['port']) == 0:
                port = "/dev/ttyACM0"
            else:
                port = "/dev/ttyUSB0"
            rate = int(para['rate'])
            if para['receive_arduino'] == "True":
                is_receive = True
            else:
                is_receive = False
            self.drive_board = CarSerial(port=port, baud_rate=rate, receive=is_receive)
        else:
            address = int(para['iic_address'], base=16)
            freq = int(para['frequency'])
            if para['receive_pca'] == "True":
                is_receive = True
            else:
                is_receive = False
            board_type = int(para['board_type'])
            self.drive_board = PCA9685(address=address, frequency=freq, debug=is_receive, board_type=board_type)
        base_speed = int(para['base_speed'])
        prop = float(para['proportional'])
        self.car_controller = CarController(car_serial=self.drive_board, base_speed=base_speed, proportional=prop)

        self.line_camera_capture = cv2.VideoCapture(self._line_camera)
        self.video = VideoWriter("video/" + time.strftime("%Y%m%d%H%M%S"), 320, 240)
        self.available_frame = None
        self.render_frame = None
        self.original_frame = None
        self.frame_rate_timer = CarTimer()
        self.display = ShowImage()
        self.is_open_window = True


        # addition
        para = cf['addition']
        if para['show_available'] == "True":
            self.is_show_available = True
        else:
            self.is_show_available = False
        if para['show_render'] == "True":
            self.is_show_render = True
        else:
            self.is_show_render = False
        if para['print_frame_rate'] == "True":
            self.is_print_frame_rate = True
        else:
            self.is_print_frame_rate = False
        if para['save_video'] == "True":
            self.is_save_video = True
        else:
            self.is_save_video = False

    def get_platform(self):
        if platform.system().lower() == 'windows':
            return 0
        elif platform.system().lower() == 'linux':
            return 1
        else:
            return 2

    def main_loop(self):
        """
                    整个程序的主循环，子类的各个操作模块，建立后，把它加入task_lisk列表，由本函数负责循环执行
                    子类不用再写循环。
        """
        # 通过摄像头读入一帧
        while True:
            ret, self.original_frame = self.line_camera_capture.read()  # 读取一帧
            size = (320, 240)  # 改变大小
            self.original_frame = cv2.resize(self.original_frame, size)
            self.render_frame = self.original_frame.copy()        # ？深拷贝

            # 循环任务列表，按顺序执行，ImageInit需要先于其他cv下面的对象执行
            for task in GuiCarBase.task_list:
                tmp = []
                if isinstance(task, ImageInit):  # 没办法弄成一样，所以写了两个if
                    self.available_frame = task.execute(self.original_frame)
                elif isinstance(task, FindRoadblock):
                    task.execute(self.original_frame, None)
                else:
                    tmp.append(self.render_frame)
                    task.execute(self.available_frame, tmp)
            # 实际的小车控制操作由update控制
            self.car_controller.update()

            self.display_window()
            if self.is_print_frame_rate:  # 这个可以取消
                self.display_frame_rate()
            if self.is_save_video:  # 保存视频
                self.video.write(self.render_frame)

            # 检测键盘，发现按下 q 键 退出循环
            if cv2.waitKey(1) == ord('q'):
                break

    def display_window(self):
        """
            显示三个窗口，大多数情况下都是需要三个窗口，所以干脆用一个函数建立把它显示出来。
        :return:
        """
        self.display.show(self.original_frame, '原始')
        if self.is_show_available:
            self.display.show(self.available_frame, '实际')
        if self.is_show_render:
            self.display.show(self.render_frame, '渲染')

    def display_frame_rate(self):
        """
            打印帧速率
        """
        print("帧速度：{} 帧/秒".format(1.0 / self.frame_rate_timer.duration()))
        self.frame_rate_timer.restart()

    def close(self):
        if self.drive_board:
            self.drive_board.drive_motor(0, 0)
            self.drive_board.close()
        if self.line_camera_capture:
            self.line_camera_capture.release()
        cv2.destroyAllWindows()
        if self.recognition:
            self.recognition.close()
        if self.video:
            self.video.release()
